﻿using System;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Text;
using ContainerVariations.Common;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using NUnit.Framework;

namespace ContainerVariations.UnitySamples
{
    [TestFixture]
    public class UnityFixture : TemplateFixture
    {
        [Test]
        public override void Configure_a_single_object_with_container_lifetime()
        {
            // Singleton is *not* the default life-cycle in Unity.
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType<IKnob, Knob>(new ContainerControlledLifetimeManager());
                var knobA = container.Resolve<IKnob>();
                var knobB = container.Resolve<IKnob>();
                Assert.That(knobA, Is.EqualTo(knobB));
            }
        }

        [Test]
        public override void Configure_a_single_object_with_transient_lifetime()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType<IKnob, Knob>();
                var knobA = container.Resolve<IKnob>();
                var knobB = container.Resolve<IKnob>();
                Assert.That(knobA, Is.Not.EqualTo(knobB));
            }
        }

        [Test]
        [Ignore("Haven't figured out how to implement this one in Unity yet.")]
        public override void Configure_a_manually_wired_dependency_graph()
        {
            throw new NotImplementedException();
        }

        [Test]
        public override void Configure_an_autowired_dependency_graph()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType<IHouse, House>();
                container.RegisterType<IDoor, Door>();
                container.RegisterType<IKnob, Knob>();
                var house = container.Resolve<IHouse>();
                Assert.That(house, Is.Not.Null);
                house.OpenDoor();
            }
        }

        [Test]
        public override void Xml_configure_an_autowired_dependency_graph()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                var section = (UnityConfigurationSection) ConfigurationManager.GetSection("unity");
                section.Containers.Default.Configure(container);

                var house = container.Resolve<IHouse>();
                Assert.That(house, Is.Not.Null);
                house.OpenDoor();
            }
        }

        [Test]
        public override void Configure_container_dynamically_supplemented_with_xml()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
                section.Containers.Default.Configure(container);

                container.RegisterType<Apple, Apple>();
                container.RegisterType<IAppleContents, AppleSeeds>();

                Assert.That(container.Resolve<Apple>(),
                    Is.TypeOf(typeof(Apple)));
                Assert.That(container.Resolve<Orange>(),
                    Is.TypeOf(typeof(Orange)));
            }
        }

        [Test]
        public override void Configure_container_dynamically_modified_by_xml()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType<Apple, Apple>();
                // This registration (AppleSeeds) should be replaced by the Worm
                //   component registration in the app.config file because the
                //   app.config registrations are applied last, and the last
                //   registered is the default.
                container.RegisterType<IAppleContents, AppleSeeds>();

                var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
                section.Containers.Default.Configure(container);

                var apple = container.Resolve<Apple>();
                Assert.That(apple.ToString(), Is.EqualTo("Apple contains: Worm"));
            }
        }

        [Test]
        public override void Configure_an_open_generic()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType(typeof (IFlattener<>), typeof (PassthruFlattener<>));
                container.RegisterType(typeof (IFlattener<Contact>), typeof (ContactFlattener));

                var contactFlattener = container.Resolve<IFlattener<Contact>>();
                var addressFlattener = container.Resolve<IFlattener<Address>>();
                Assert.That(contactFlattener, Is.TypeOf(typeof(ContactFlattener)));
                Assert.That(addressFlattener, Is.TypeOf(typeof(PassthruFlattener<Address>)));
            }
        }

        [Test]
        public override void Configure_a_type_with_a_lambda_or_delegate_dependency()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                const string expectedValue = "This is the value we're assigning.";
                string actualValue = null;

                Action<string> action = x => actualValue = x;

                container.RegisterType<ITestActor, TestActor>();
                container.RegisterInstance(typeof (Action<string>), action);

                var actor = container.Resolve<ITestActor>();
                actor.Act(expectedValue);

                Assert.That(actualValue, Is.EqualTo(expectedValue));
            }
        }

        [Test]
        public override void Configure_all_classes_of_a_specified_type_in_a_specified_assembly()
        {
            // This version of Unity doesn't really do this.  Instead you're got to manually
            //   get all the types (GetTypesFromAssembly) and register them yourself.

            using (IUnityContainer container = new UnityContainer())
            {
                var widgetTypes = GetTypesFromAssembly(typeof(IWidget), typeof(IWidget).Assembly);
                foreach (var widgetType in widgetTypes)
                {
                    // Won't work unless the registration is with a name!
                    container.RegisterType(typeof (IWidget), widgetType, widgetType.Name);
                }

                var widgets = container.ResolveAll<IWidget>();

                foreach (var widget in widgets)
                {
                    Console.WriteLine(widget.GetType().Name + ": " + widget.Call());
                }
                Assert.That(widgets.Count(), Is.EqualTo(4));
            }
        }

        [Test]
        public override void Configure_a_class_with_multiple_decorators()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType<IDoorUser, DoorUser>(new InjectionConstructor(true));
                container.RegisterType<IKnob, Knob>();
                container.RegisterType<IDoor, Door>("base-door");
                container.RegisterType<IDoor, SecureDoor>("secure-door",
                                                          new InjectionConstructor(
                                                              new ResolvedParameter(typeof (IDoor), "base-door"),
                                                              new ResolvedParameter(typeof (IDoorUser))));
                container.RegisterType<IDoor, EmailDoor>(
                    new InjectionConstructor(new ResolvedParameter(typeof (IDoor), "secure-door")));

                var door = container.Resolve<IDoor>();
                door.Open();
            }
        }

        [Test]
        public override void Configure_a_class_with_scalar_dependencies_from_configuration_api()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                bool mayOpenDoor = Convert.ToBoolean(ConfigurationManager.AppSettings["mayOpenDoor"]);
                container.RegisterType<IDoorUser, DoorUser>(new InjectionConstructor(mayOpenDoor));
                var doorUser = container.Resolve<IDoorUser>();
                Assert.That(doorUser.MayOpenDoor, Is.EqualTo(mayOpenDoor));
            }
        }

        [Test]
        public override void Container_can_use_an_instance_created_outside_the_container()
        {
            var stringBuilder = new StringBuilder();
            var writer = new StringWriter(stringBuilder);
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterInstance<TextWriter>(writer);
                container.RegisterType<ISender, TextWriterSender>();

                var sender = container.Resolve<ISender>();

                const string message = "This is the message to send.";
                sender.Send(message);
                Assert.That(stringBuilder.ToString(), Is.EqualTo(message));
            }
        }

        [Test]
        public override void Disposable_classes_with_container_lifetime_are_disposed_when_container_is_disposed()
        {
            IKnob knob;
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType<IKnob, Knob>(new ContainerControlledLifetimeManager());
                knob = container.Resolve<IKnob>();
                Assert.That(knob.IsDisposed, Is.False);
            }
            Assert.That(knob.IsDisposed, Is.True);
        }

        [Test]
        [Ignore("Unity does not retain references to components unless they have container lifetime, so it cannot dispose transient components when the container is disposed.")]
        public override void Disposable_classes_with_transient_lifetime_are_disposed_when_container_is_disposed()
        {
            // This test fails.  I presume this is because Unity does not
            //   keep a reference to its components when they are created
            //   for each request.
            IKnob knob;
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType<IKnob, Knob>();
                knob = container.Resolve<IKnob>();
                Assert.That(knob.IsDisposed, Is.False);
            }
            Assert.That(knob.IsDisposed, Is.True);
        }

        [Test]
        public override void Register_components_with_scalar_dependencies()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType<Answer>(new InjectionConstructor(42));
                Assert.That(container.Resolve<Answer>().Value, Is.EqualTo(42));
            }
        }

        [Test]
        [Ignore("Unity does not provide a way to pass in dependencies at resolution.")]
        public override void Resolve_components_providing_scalar_dependencies()
        {
            using (IUnityContainer container = new UnityContainer())
            {
                container.RegisterType<Answer>();
                var answer = container.Resolve<Answer>();
                Assert.That(answer.Value, Is.EqualTo(42));
            }
        }
    }
}